﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using WindowsPhone.CompactContainer.ServiceLocation.CompactContainer;
using WindowsPhone.TestFramework.Tools;

namespace WindowsPhone.CompactContainer.Tests.ServiceLocation.CompactContainer
{
    [TestClass]
    public class CompactContainerTests
    {
        private Container _container;

        [TestInitialize]
        public void Init()
        {
            _container = new Container();
        }

        [TestMethod]
        public void CanAddComponent()
        {
            Assert.IsFalse(_container.HasComponent("comp.a"));
            Assert.IsFalse(_container.HasComponent(typeof(IComponentA)));

            _container.AddComponent<IComponentA, ComponentA>("comp.a");

            Assert.IsTrue(_container.HasComponent(typeof(IComponentA)));
            Assert.IsTrue(_container.HasComponent("comp.a"));
        }

        [TestMethod]
        public void CannotAddDuplicatedComponent()
        {
            _container.AddComponent("comp", typeof(ComponentA));

            ExtendedAssert.Throws<ContainerException>(() => _container.AddComponent("comp", typeof (ComponentB)));
    
        }

        [TestMethod]
        public void CanResolveSingletonComponent()
        {
            _container.AddComponent("comp.a", typeof(IComponentA), typeof(ComponentA));

            var compA1 = (IComponentA)_container.Resolve(typeof(IComponentA));
            var compA2 = (IComponentA)_container.Resolve("comp.a");

            Assert.IsNotNull(compA1);
            Assert.AreSame(compA1, compA2);
        }

        [TestMethod]
        public void CanResolveSingletonComponentUsingGenerics()
        {
            _container.AddComponent("comp.a", typeof(IComponentA), typeof(ComponentA));

            var compA1 = _container.Resolve<IComponentA>();
            var compA2 = _container.Resolve<IComponentA>("comp.a");

            Assert.IsNotNull(compA1);
            Assert.AreSame(compA1, compA2);
        }

        [TestMethod]
        public void CanResolveSingletonComponentWithConstructorInjection()
        {
            _container.AddComponent("comp.a", typeof(IComponentA), typeof(ComponentA));
            _container.AddComponent("comp.b", typeof(IComponentB), typeof(ComponentB));

            var compB = (IComponentB)_container.Resolve(typeof(IComponentB));

            Assert.AreSame(_container.Resolve(typeof(IComponentA)), compB.CompA);
        }

        [TestMethod]
        public void CanResolveTransientComponent()
        {
            _container.AddComponent("comp.a", typeof(IComponentA), typeof(ComponentA), LifestyleType.Transient);

            var compA1 = _container.Resolve<IComponentA>();
            var compA2 = _container.Resolve<IComponentA>();

            Assert.AreNotSame(compA1, compA2);
        }

        [TestMethod]
        public void CanRegisterAndResolveComponentWithoutService()
        {
            _container.AddComponent("comp.a", typeof(ComponentA));

            var compA1 = _container.Resolve<ComponentA>();
            var compA2 = _container.Resolve<ComponentA>();
            Assert.AreSame(compA2, compA1);
        }

        [TestMethod]
        public void CanAddComponentWithInstance()
        {
            IComponentA compA1 = new ComponentA();

            _container.AddComponentInstance("comp.a1", compA1);
            Assert.AreSame(compA1, _container.Resolve<ComponentA>());

            _container.AddComponentInstance("comp.a2", compA1);
            Assert.AreSame(compA1, _container.Resolve<IComponentA>("comp.a2"));

            _container.AddComponentInstance("comp.a3", typeof(IComponentA), compA1);
            Assert.AreSame(compA1, _container.Resolve<IComponentA>());

            _container.AddComponentInstance<IComponentA>(compA1);
            Assert.AreSame(compA1, _container.Resolve<IComponentA>("IComponentA"));

        }


        [TestMethod]
        public void RegisterSelf()
        {
            Assert.AreSame(_container, _container.Resolve<IContainer>());
        }

        [TestMethod]
        public void CanGetAllComponentsThatImplementService()
        {
            _container.AddComponent("comp.a1", typeof(IComponentA), typeof(ComponentA));
            _container.AddComponent("comp.a2", typeof(IComponentA), typeof(ComponentAA));
            _container.AddComponent("comp.b", typeof(IComponentB), typeof(ComponentB));

            object[] compsA1 = _container.GetServices(typeof(IComponentA));
            Assert.AreEqual(2, compsA1.Length);

            var compsA2 = _container.GetServices<IComponentA>();
            Assert.AreEqual(2, compsA1.Length);

            Assert.AreEqual((IComponentA)compsA1[0], compsA2[0]);
            Assert.AreEqual((IComponentA)compsA1[1], compsA2[1]);
        }

        [TestMethod]
        public void DetectCircularReferences()
        {
            _container.AddComponent("x", typeof(IDependentX), typeof(DependentX));
            _container.AddComponent("y", typeof(IDependentY), typeof(DependentY));

            ExtendedAssert.Throws<ContainerException>(() => _container.Resolve<IDependentY>());
        }

        [TestMethod]
        public void CanResolveToClassTypeIfNotSuchService()
        {
            _container.AddComponent("comp.a", typeof(IComponentA), typeof(ComponentA));

            var compa = _container.Resolve<ComponentA>();

            Assert.IsNotNull(compa);
        }

        [TestMethod]
        public void CountSingletonsInitialized()
        {
            _container.AddComponent("comp.a1", typeof(IComponentA), typeof(ComponentA), LifestyleType.Transient);
            _container.AddComponent("comp.b", typeof(IComponentB), typeof(ComponentB));
            Assert.AreEqual(_container.SingletonInstanceCount, 1);		

            _container.AddComponentInstance("comp.a2", new ComponentA());
            Assert.AreEqual(_container.SingletonInstanceCount, 2);

            _container.Resolve<IComponentA>();
            Assert.AreEqual(_container.SingletonInstanceCount, 2);

            _container.Resolve<IComponentB>();
            Assert.AreEqual(_container.SingletonInstanceCount, 3);
        }

        [TestMethod]
        public void CanResolveWithHandler()
        {
            _container.RegisterHandler<IStartable>(new StartableHandler());
            _container.AddComponent("startable.service", typeof(IComponentU), typeof(StartableComponent));

            var cu = _container.Resolve<IComponentU>();

            Assert.AreEqual(1, cu.A);
        }

    }

    public interface IComponentA
    {
    }

    public interface IComponentB
    {
        IComponentA CompA { get; }
    }

    public class ComponentA : IComponentA
    {
    }

    public class ComponentAA : IComponentA
    {
    }

    public class ComponentB : IComponentB
    {
        private readonly IComponentA compA;

        public IComponentA CompA
        {
            get { return compA; }
        }

        public ComponentB(IComponentA compA)
        {
            this.compA = compA;
        }
    }


    public class StartableHandler : AbstractHandler
    {
        public override object Create(Type classType)
        {
            IStartable product = (IStartable)Container.DefaultHandler.Create(classType);
            product.Start();
            return product;
        }
    }

    public interface IStartable
    {
        void Start();
        void Stop();
    }

    public class StartableComponent : IComponentU, IStartable
    {
        public int A { get; set; }

        public void Start()
        {
            A = 1;
        }

        public void Stop()
        {
            A = 2;
        }
    }

    public interface IComponentU
    {
        int A { get; set; }
    }

    public interface IDependentX
    {
    }

    public class DependentX : IDependentX
    {
        private readonly IDependentY _y;

        public DependentX(IDependentY y)
        {
            this._y = y;
        }
    }

    public interface IDependentY
    {
    }

    public class DependentY : IDependentY
    {
        private readonly IDependentX x;

        public DependentY(IDependentX x)
        {
            this.x = x;
        }
    }

}
